﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Lexer {
	public delegate T TokenCreator<T>(int pos, string lexeme);

	class AmbiguousException : Exception { }

	public class Lexer<T> where T : class {
		public TokenCreator<T> UnrecognizedCallback;
		public TokenCreator<T> AmbiguousCallback;
		public TokenCreator<T> EofCallback;

		private Node<T> root = new Node<T>();

		public void RegisterPattern(LexerPattern pattern, TokenCreator<T> callback) {
			Node<T> curr = root;
			foreach (CharPattern cp in pattern) {
				curr = curr.getOrCreateOutgoing(cp);
			}
			if (curr.Callback != null) {
				throw new InvalidOperationException("Ambiguity detected! The pattern '" + pattern.ToString() + "' already matches a callback.");
			}
			curr.Callback = callback;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="input"></param>
		/// <returns></returns>
		/// <exception cref="Exception">If an ambiguity or an unrecognized token were encountered,
		/// but no callback is set to handle them.</exception>
		public IEnumerable<T> Scan(string input) {
			Node<T> curr = root;
			int pos = 0;
			int start = -1;
			StringBuilder lexeme = new StringBuilder();
			while (pos < input.Length) {
				char c = input[pos];
				Node<T> next = null;
				bool caught = false;
				if (curr == root) start = pos;
				try {
					next = curr.getOutgoing(c);
				} catch (AmbiguousException) {
					caught = true;
				}
				if (caught) {
					// Here instead of in the catch clause because
					// "yield return" is not supported in a catch clause.
					if (AmbiguousCallback == null) {
						throw new Exception("Ambiguity encountered, but no ambiguous callback is set! Please fix the lexing rules or set the AmbiguousCallback property.");
					}
					lexeme.Append(c);
					pos++;
					T result = AmbiguousCallback(start, lexeme.ToString());
					if (result != null) yield return result;
					lexeme.Clear();
					curr = root;
					continue;
				}
				if (next == null) {
					if (curr == root) {
						// This means an unrecognized character altogether.
						// To prevent an infinite loop, eat at least one character in that case
						lexeme.Append(c);
						pos++;
					}
					// No more chars can be added to the current lexeme and still match anything.
					// That means that if we currently have a matching callback, use it -
					// otherwise this is an unrecognized token.
					// Either way reset the lexeme and return to the root.
					T result = null;
					if (curr.Callback != null) {
						result = curr.Callback(start, lexeme.ToString());
					} else if (UnrecognizedCallback != null) {
						result = UnrecognizedCallback(start, lexeme.ToString());
					} else {
						throw new Exception("Unrecognized token encountered, but no unrecognized token callback is set! Please fix the lexing rules or set the UnrecognizedCallback property.");
					}
					if (result != null) yield return result;
					lexeme.Clear();
					curr = root;
				} else {
					// We can continue building the lexeme.
					lexeme.Append(c);
					pos++;
					curr = next;
				}
			}
			// We've reached the end of the file.
			if (lexeme.Length > 0) {
				// If we still have data in our lexeme, we need to accept it as a token
				// or call the unrecognized callback, if we can't.
				if (curr.Callback != null) {
					yield return curr.Callback(start, lexeme.ToString());
				} else if (UnrecognizedCallback != null) {
					yield return UnrecognizedCallback(start, lexeme.ToString());
				} else {
					throw new Exception("Unrecognized token encountered, but no unrecognized token callback is set! Please fix the lexing rules or set the UnrecognizedCallback property.");
				}
			}
			if (EofCallback != null) yield return EofCallback(pos, "");
		}
	}
}
